"""
$Id: crossover.py 2 2007-03-04 19:08:44Z sidnei.da.silva $
"""

__metaclass__ = type

from evolve.choice import randomize, choice
from evolve.genome import Genome

class Crossover(object):
    """Crossover policy: Combine two genomes and generate two childs
    """

    def __call__(self, x, y):
        raise NotImplementedError, self.__class__


class SimpleCrossover(Crossover):

    def __init__(self, chance=0.6, choice=randomize):
        """
        chance - a float between 0 and 1, normally bigger than 0.6
        which encodes the probability that two selected organisms will
        actually breed.
        choice - a callable that will generate a float between 0 and 1
        """
        self.chance = chance
        self.choice = choice

    def __call__(self, x, y):
        """Will only breed if the generated choice is smaller than chance
        """
        if self.choice() > self.chance:
            return x,


class StepsCrossover(SimpleCrossover):

    def __init__(self, steps=((-3, -1),), chance=0.6, choice=randomize):
        """
        steps - sequence of tuples in the form (start, end)
        where for each tuple a crossover will be done between
        the genomes
        """
        super(StepsCrossover, self).__init__(chance, choice)
        self.steps = steps


class RangeCrossover(StepsCrossover):
    """Crossover a fixed set of genes between genomes
    """

    def __call__(self, x, y):
        """Will only breed if the generated choice is bigger than chance
        """
        result = super(RangeCrossover, self).__call__(x, y)
        if result is not None:
            return result
        z = tuple(x)
        for start, end in self.steps:
            z = z[:start] + y[start:end] + z[end:]
        return z,

class NonRepeatRangeCrossover(StepsCrossover):
    """Crossover a fixed range of genes between genomes, without
    repeating genes, keeping the gene ordering from the originating
    genome.
    """

    def __call__(self, x, y):
        """Will only breed if the generated choice is bigger than chance
        """
        result = super(NonRepeatRangeCrossover, self).__call__(x, y)
        if result is not None:
            return result

        z = list(x)

        for start, end in self.steps:
            for i in range(start, end):
                gene = y[i]
                z.remove(gene)
                z.insert(-i, gene)
        return Genome(*z),

class NonRepeatPairCrossover(StepsCrossover):
    """Crossover a fixed pair of genes between genomes, without
    repeating genes, keeping the gene ordering from the originating
    genome.
    """

    def __call__(self, x, y):
        """Will only breed if the generated choice is bigger than chance
        """
        result = super(NonRepeatPairCrossover, self).__call__(x, y)
        if result is not None:
            return result

        z = list(x)
        y = list(y)

        for start, end in self.steps:
            for i in (start, end):
                gene = y[i]
                z.remove(gene)
                z.insert(i, gene)
        return Genome(*z),

class NonRepeatRandomCrossover(SimpleCrossover):
    """Crossover a random gene between genomes, without
    repeating genes
    """

    def __call__(self, x, y):
        """Will only breed if the generated choice is bigger than chance
        """
        result = super(NonRepeatRandomCrossover, self).__call__(x, y)
        if result is not None:
            return result

        z = list(x)
        y = list(y)
        gene = choice(y)
        index = y.index(gene)
        z.remove(gene)
        position = int(self.choice() * len(y))
        z.insert(position, gene)
        return Genome(*z),


class ComboNonRepeatCrossover(SimpleCrossover):

    def __init__(self, chance=0.6, choice=randomize, steps=((1, 5),)):
        super(ComboNonRepeatCrossover, self).__init__(chance, choice)
        self.random = NonRepeatRandomCrossover(chance=1, choice=choice)
        self.range = NonRepeatRangeCrossover(chance=1, choice=choice,
                                             steps=steps)

        def generator():
            while True:
                yield self.random
                yield self.range

        self.generator = generator()

    def __call__(self, x, y):
        """Will only breed if the generated choice is bigger than chance
        """
        result = super(ComboNonRepeatCrossover, self).__call__(x, y)
        if result is not None:
            return result
        crossover = self.generator.next()
        return crossover(x, y)
